#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Math;
using Weazel.Gezel.Model;
using Weazel.Gezel.Simulator.Reference.Profiling;

#endregion

namespace Weazel.Gezel.Simulator.Reference
{
    abstract class UnaryOperationSignal : AbstractSignal
    {
        protected Profiler profiler;

        /// <summary>
        /// Caches evaluation results
        /// </summary>
        protected Integer value;

        /// <summary>
        /// Child signal
        /// </summary>
        protected readonly AbstractSignal left;

        /// <summary>
        /// Constructs a new UnaryOperation signal which
        /// operates on the specified child signal
        /// </summary>
        /// <param name="id"></param>
        /// <param name="left"></param>
        public UnaryOperationSignal(Symid id, AbstractSignal left, Simulation sim)
            : base(id, null)
        {
            this.left = left;
            this.value = new Integer((int)this.Width(), 0, this.Signed() == 1 ? true : false);

            if (sim.profiler != null)
            {
                profiler = sim.profiler;
                profiler.MakeOperation(this, Width());
            }
        }

        /// <summary>
        /// Constructs a new UnaryOperation signal which
        /// operates on the specified child signal
        /// </summary>
        /// <param name="id"></param>
        /// <param name="left"></param>
        /// <param name="wlen"></param>
        /// <param name="sign"></param>
        public UnaryOperationSignal(Symid id, AbstractSignal left, int wlen, int sign, Simulation sim)
            : base(id, null)
        {
            this.left = left;
            this.value = new Integer(wlen, sign == 1);

            if (sim.profiler != null)
            {
                profiler = sim.profiler;
                profiler.MakeOperation(this, Width());
            }
        }

        internal override Integer PeekValue()
        {
            return value;
        }

        /// <summary>
        /// Called to determine if signal needs evaluation
        /// in the current cycle
        /// </summary>
        /// <param name="g">current simulation cycle</param>
        /// <returns>true iff signal needs evaluation, false otherwise</returns>
        public override bool IsGeneration(long g)
        {
            return this.generation == g;
        }

        internal override uint Width()
        {
            return left.Width();
        }

        internal override uint Signed()
        {
            return left.Signed();
        }

        public override bool TraceToSignal(Symid toSignalId, List<AbstractSignal> signals)
        {
            return left.TraceToSignal(toSignalId, signals);
        }
    }

    class CastSignal : UnaryOperationSignal
    {
        public CastSignal(Symid id, int bitwidth, int sign, AbstractSignal left, Simulation sim)
            : base(id, left, (int)left.Width(), (int)left.Signed(), sim)
        {
            value = new Integer(bitwidth, sign == 1 ? true : false);
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            value.ValueCopy(left.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }

        internal override uint Width()
        {
            return (uint)value.Width;
        }

        internal override uint Signed()
        {
            return value.Signed ? 1u : 0u;
        }
    }

    class NotSignal : UnaryOperationSignal
    {
        public NotSignal(Symid id, AbstractSignal left, Simulation sim)
            : base(id, left, (int)left.Width(), (int)left.Signed(), sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer.Not(ref value, left.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class NegSignal : UnaryOperationSignal
    {
        public NegSignal(Symid id, AbstractSignal left, Simulation sim)
            : base(id, left, (int)left.Width(), (int)left.Signed(), sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer.Negate(ref value, left.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class LookupSignal : UnaryOperationSignal
    {
        private LookupTable lut;

        public LookupSignal(Symid id, AbstractSignal left, LookupTable lut, Simulation sim)
            : base(id, left, lut.Width, lut.Signed ? 1 : 0, sim)
        {
            this.lut = lut;
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer i = left.Evaluate(ip, targetGeneration);
            value.ValueCopy(lut[i.ToInt()]);
            return value;
        }

        /// <summary>
        /// Need to override this since the
        /// width of the LookupSignal is determined
        /// by the width of the underlying lookup table.
        /// </summary>
        /// <returns>width of the lut</returns>
        internal override uint Width()
        {
            return (uint)lut.Width;
        }

        /// <summary>
        /// Need to override this since the
        /// width of the LookupSignal is determined
        /// by the sign of the underlying lookup table.
        /// </summary>
        /// <returns>sign of the lut</returns>
        internal override uint Signed()
        {
            return lut.Signed ? 1u : 0u;
        }
    }

    abstract class BinaryOperationSignal : AbstractSignal
    {
        protected Profiler profiler;
        protected Integer value;
        protected AbstractSignal left, right;

        public BinaryOperationSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, null)
        {
            this.left = left;
            this.right = right;
            this.value = new Integer((int)this.Width(), 0, this.Signed() == 1 ? true : false);

            if (sim.profiler != null)
            {
                profiler = sim.profiler;
                profiler.MakeOperation(this, Width());
            }
        }

        public BinaryOperationSignal(Symid id, AbstractSignal left, AbstractSignal right, int wlen, int sign, Simulation sim)
            : base(id, null)
        {
            this.left = left;
            this.right = right;
            this.value = new Integer(wlen, sign == 1);

            if (sim.profiler != null)
                sim.profiler.MakeOperation(this, wlen);
        }


        internal override Integer PeekValue()
        {
            return value;
        }

        internal override uint Width()
        {
            uint res = left.Width() + right.Width();
            return res;
        }

        internal override uint Signed()
        {
            return System.Math.Max(left.Signed(), right.Signed());
        }

        /// <summary>
        /// Called to determine if signal needs evaluation
        /// in the current cycle
        /// </summary>
        /// <param name="g">current simulation cycle</param>
        /// <returns>true iff signal needs evaluation, false otherwise</returns>
        public override bool IsGeneration(long g)
        {
            return generation == g;
        }

        public override bool TraceToSignal(Symid toSignalId, List<AbstractSignal> signals)
        {
            List<AbstractSignal> tempSignals = new List<AbstractSignal>();
            bool success = left.TraceToSignal(toSignalId, tempSignals);
            if (success)
            {
                signals.AddRange(tempSignals);
                return true;
            }
            tempSignals.Clear();
            success = right.TraceToSignal(toSignalId, tempSignals);
            if (success)
            {
                signals.AddRange(tempSignals);
                return true;
            }

            return false;
        }
    }

    class AddSignal : BinaryOperationSignal
    {
        public AddSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer.Add(ref value, left.Evaluate(ip, targetGeneration), right.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class SubSignal : BinaryOperationSignal
    {
        public SubSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer.Subtract(ref value, left.Evaluate(ip, targetGeneration), right.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class MulSignal : BinaryOperationSignal
    {
        public MulSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, (int)(left.Width() + right.Width()), left.Signed() == 1 || right.Signed() == 1 ? 1 : 0, sim)
        {            
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            // note: error where mul would "sum up" when called multiple times
            //       fix:

            value.Assign(0);

            Integer.Multiply(ref value, left.Evaluate(ip, targetGeneration), right.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class ModSignal : BinaryOperationSignal
    {
        public ModSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        /// <summary>
        /// The result of a mod operation may be negative. If the right hand side
        /// is positive 10 it may be represented using only 4 bits but a max of 4
        /// bits on the result will cause the result of -1 % 10 to become -7 since
        /// 4 bits are insufficient to represent -9. 
        /// </summary>        
        internal override uint Width()
        {
            return System.Math.Max(left.Width() + 1, right.Width() + 1);
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer.Mod(ref value, left.Evaluate(ip, targetGeneration), right.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class DivSignal : BinaryOperationSignal
    {
        public DivSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer.Divide(ref value, left.Evaluate(ip, targetGeneration), right.Evaluate(ip, targetGeneration));
            return value;
        }
    }

    class AndSignal : BinaryOperationSignal
    {
        public AndSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer.And(ref value, left.Evaluate(ip, targetGeneration), right.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class IorSignal : BinaryOperationSignal
    {
        public IorSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer.Or(ref value, left.Evaluate(ip, targetGeneration), right.Evaluate(ip, targetGeneration));

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class XorSignal : BinaryOperationSignal
    {
        public XorSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer l = left.Evaluate(ip, targetGeneration);
            Integer r = right.Evaluate(ip, targetGeneration);
            Integer.Xor(ref value, l, r);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class ShrSignal : BinaryOperationSignal
    {
        public ShrSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer l = left.Evaluate(ip, targetGeneration);
            Integer r = right.Evaluate(ip, targetGeneration);
            value.ValueCopy(l >> r.ToInt());

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class ConcatenationSignal : BinaryOperationSignal
    {
        public ConcatenationSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer l = left.Evaluate(ip, targetGeneration);
            Integer r = right.Evaluate(ip, targetGeneration);
            Integer.Concat(ref value, l, r);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }

        internal override uint Width()
        {
            return left.Width() + right.Width();
        }


        internal override uint Signed()
        {
            return left.Signed();
        }

    }

    class ShlSignal : BinaryOperationSignal
    {
        public ShlSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer l = left.Evaluate(ip, targetGeneration);
            Integer r = right.Evaluate(ip, targetGeneration);
            Integer.ShiftLeft(ref value, l, r.ToInt());

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }

        internal override uint Width()
        {
            return (left.Width() + (uint)(1 << (int)right.Width()));
        }
    }

    class EqSignal : BinaryOperationSignal
    {
        public EqSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer l = left.Evaluate(ip, targetGeneration);
            Integer r = right.Evaluate(ip, targetGeneration);

            bool areEqual = l == r;
            if (areEqual)
                value.ValueCopy(Integer.One);
            else
                value.ValueCopy(Integer.Zero);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }

        /// <summary>
        /// Logic operations have a bitwidth of one
        /// </summary>
        /// <returns>one</returns>
        internal override uint Width()
        {
            return 1u;
        }

        /// <summary>
        /// Logic operations are unsigned
        /// </summary>
        /// <returns></returns>
        internal override uint Signed()
        {
            return 0u;
        }
    }

    class NeqSignal : BinaryOperationSignal
    {
        public NeqSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            bool areEqual = left.Evaluate(ip, targetGeneration) == right.Evaluate(ip, targetGeneration);
            if (areEqual)
                value.ValueCopy(Integer.Zero);
            else
                value.ValueCopy(Integer.One);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }
    }

    class LtSignal : BinaryOperationSignal
    {
        public LtSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            bool isLess = left.Evaluate(ip, targetGeneration) < right.Evaluate(ip, targetGeneration);
            if (isLess)
                value.ValueCopy(Integer.One);
            else
                value.ValueCopy(Integer.Zero);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }

        /// <summary>
        /// Logic operations have a bitwidth of one
        /// </summary>
        /// <returns>one</returns>
        internal override uint Width()
        {
            return 1u;
        }

        /// <summary>
        /// Logic operations are unsigned
        /// </summary>
        /// <returns></returns>
        internal override uint Signed()
        {
            return 0u;
        }
    }

    class GtSignal : BinaryOperationSignal
    {
        public GtSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            bool isGreater = left.Evaluate(ip, targetGeneration) > right.Evaluate(ip, targetGeneration);
            if (isGreater)
                value.ValueCopy(Integer.One);
            else
                value.ValueCopy(Integer.Zero);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }

        /// <summary>
        /// Logic operations have a bitwidth of one
        /// </summary>
        /// <returns>one</returns>
        internal override uint Width()
        {
            return 1u;
        }

        /// <summary>
        /// Logic operations are unsigned
        /// </summary>
        /// <returns></returns>
        internal override uint Signed()
        {
            return 0u;
        }
    }

    class LteSignal : BinaryOperationSignal
    {
        public LteSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            bool isLessEqual = left.Evaluate(ip, targetGeneration) <= right.Evaluate(ip, targetGeneration);
            if (isLessEqual)
                value.ValueCopy(Integer.One);
            else
                value.ValueCopy(Integer.Zero);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }

        /// <summary>
        /// Logic operations have a bitwidth of one
        /// </summary>
        /// <returns>one</returns>
        internal override uint Width()
        {
            return 1u;
        }

        /// <summary>
        /// Logic operations are unsigned
        /// </summary>
        /// <returns></returns>
        internal override uint Signed()
        {
            return 0u;
        }
    }

    class GteSignal : BinaryOperationSignal
    {
        public GteSignal(Symid id, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, left, right, sim)
        {
        }

        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            bool areGreaterEqual = left.Evaluate(ip, targetGeneration) >= right.Evaluate(ip, targetGeneration);
            if (areGreaterEqual)
                value.ValueCopy(Integer.One);
            else
                value.ValueCopy(Integer.Zero);

            if (profiler != null)
                profiler.EvaluateOperation(Id, value);

            return value;
        }

        /// <summary>
        /// Logic operations have a bitwidth of one
        /// </summary>
        /// <returns>one</returns>
        internal override uint Width()
        {
            return 1u;
        }

        /// <summary>
        /// Logic operations are unsigned
        /// </summary>
        /// <returns></returns>
        internal override uint Signed()
        {
            return 0u;
        }
    }


    class TernarySignal : AbstractSignal
    {
        private Profiler profiler;
        protected Integer v;
        protected AbstractSignal condition, left, right;

        public TernarySignal(Symid id, AbstractSignal condition, AbstractSignal left, AbstractSignal right, Simulation sim)
            : base(id, null
    )
        {
            this.v = new Integer(
              (int)System.Math.Max(left.Width(), right.Width()),
              0,
              (left.Signed() | right.Signed()) > 0 ? true : false
            );
            this.condition = condition;
            this.left = left;
            this.right = right;

            if (sim.profiler != null)
            {
                profiler = sim.profiler;
                profiler.MakeOperation(this, Width());
            }
        }

        /// <summary>
        /// NOTE: Gezel does not use lazy evaluation here!
        /// </summary>
        /// <param name="targetGeneration"></param>
        /// <returns></returns>
        internal override Integer Evaluate(AbstractIpBlock ip, long targetGeneration)
        {
            Integer c = condition.Evaluate(ip, targetGeneration);

            // check condition
            if (c.IsZero())
            {
                // condition was false
                Integer r = right.Evaluate(ip, targetGeneration);
                v.ValueCopy(r);
            }
            else
            {
                // condition was true
                Integer l = left.Evaluate(ip, targetGeneration);
                v.ValueCopy(l);
            }

            if (profiler != null)
                profiler.EvaluateOperation(Id, v);

            return v;
        }

        internal override Integer PeekValue()
        {
            return v;
        }

        public override bool IsGeneration(long g)
        {
            return false;
        }

        internal override uint Width()
        {
            return System.Math.Max(left.Width(), right.Width());
        }

        internal override uint Signed()
        {
            return System.Math.Max(left.Signed(), right.Signed());
        }

        public override bool TraceToSignal(Symid toSignalId, List<AbstractSignal> signals)
        {
            List<AbstractSignal> tempSignals = new List<AbstractSignal>();
            bool success = condition.TraceToSignal(toSignalId, tempSignals);
            if (success)
            {
                signals.AddRange(tempSignals);
                return true;
            }
            tempSignals.Clear();

            success = left.TraceToSignal(toSignalId, tempSignals);
            if (success)
            {
                signals.AddRange(tempSignals);
                return true;
            }
            tempSignals.Clear();

            success = right.TraceToSignal(toSignalId, tempSignals);
            if (success)
            {
                signals.AddRange(tempSignals);
                return true;
            }

            return false;
        }
    }
}
